#pragma once
// -------------------------------------------------
// ZED Kit
// -------------------------------------------------
//   File Name: locale.hpp
//      Author: Ziming Li
//     Created: 2023-11-16
// -------------------------------------------------
// Copyright (C) 2023 MingYang Software Technology.
// -------------------------------------------------

#ifndef ZED_LOCALE_HPP
#define ZED_LOCALE_HPP

#include "./platform_sdk.h"
#include "./string.hpp"

namespace zed {

class to_u8
{
public:
    explicit to_u8(char32_t c);

    operator std::string_view() const { return std::string_view(m_buffer, m_length); }
private:
    char m_buffer[4];
    size_t m_length = 0;
};

template <typename CharT, typename E = std::enable_if_t<(sizeof(CharT) > sizeof(char))>>
class u8_iterator
{
public:
    u8_iterator(void) = default;
    u8_iterator(const char* ps, size_t left, CharT error_char = '?');

    CharT operator*(void) const;
    bool operator==(const u8_iterator& o) const;
    bool operator!=(const u8_iterator& o) const { return !(*this == o); }
    void operator++();

    bool eof(void) const { return 0 == m_left; }
    size_t current_char_length(void) const { return m_current_char_length; }
private:
    void advance(size_t count);

    const CharT m_error_char = '\0';
    const char* m_ps = nullptr;
    size_t m_left = 0;
    size_t m_current_char_length = 0;
};

template <typename CharT, typename E = std::enable_if_t<(sizeof(CharT) > sizeof(char))>>
class to_utf
{
public:
    explicit to_utf(std::string_view u8s, CharT error_char = '?');

    u8_iterator<CharT> begin(void) const;
    u8_iterator<CharT> end(void) const;
private:
    const std::string_view m_u8s;
    const CharT m_error_char;
};
using to_u16 = to_utf<char16_t>;
using to_u32 = to_utf<char32_t>;

#ifdef _Z_OS_WINDOWS
std::wstring multi_byte_to_wide_string(std::string_view s, UINT cp = CP_UTF8);
std::wstring multi_byte_to_wide_string(PCSTR psz, UINT cp = CP_UTF8);
inline std::wstring multi_byte_to_wide_string(const std::string &s, UINT cp = CP_UTF8) { return multi_byte_to_wide_string(std::string_view(s), cp); }

std::string wide_string_to_multi_byte(std::wstring_view s, UINT cp = CP_UTF8);
std::string wide_string_to_multi_byte(PCWSTR psz, UINT cp = CP_UTF8);
inline std::string wide_string_to_multi_byte(const std::wstring &s, UINT cp = CP_UTF8) { return wide_string_to_multi_byte(std::wstring_view(s), cp); }
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementations

namespace detail {

inline size_t adjust_char_length(const char* ps, size_t left)
{
    do {
        if (0 == left)
            break;

        unsigned char c1 = static_cast<unsigned char>(*ps);
        if (0 == (0x80 & c1))
            return 1;

        if (1 == left)
            break;

        unsigned char c2 = static_cast<unsigned char>(ps[1]);
        if (0xc0 == (0xe0 & c1))
        {
            if (0x80 == (0xc0 & c2))
                return 2;
            break;
        }

        if (2 == left)
            break;

        unsigned char c3 = static_cast<unsigned char>(ps[2]);
        if (0xe0 == (0xf0 & c1))
        {
            if (0x80 == (0xc0 & c2) && 0x80 == (0xc0 & c3))
                return 3;
            break;
        }

        if (3 == left)
            break;

        unsigned char c4 = static_cast<unsigned char>(ps[3]);
        if (0xf0 == (0xf8 & c1))
        {
            if (0x80 == (0xc0 & c2) && 0x80 == (0xc0 & c3) && 0x80 == (0xc0 & c4))
                return 4;
        }
    } while (false);

    return 0;
}

#ifdef _Z_OS_WINDOWS
inline std::wstring multi_byte_to_wide_string(PCSTR ps, int length, UINT cp)
{
    std::wstring ret;
    int r = ::MultiByteToWideChar(cp, 0, ps, length, nullptr, 0);
    if (r > 0)
    {
        ret.resize(r);
        ::MultiByteToWideChar(cp, 0, ps, length, const_cast<PWSTR>(ret.c_str()), r + 1);
    }
    return ret;
}

inline std::string wide_string_to_multi_byte(PCWSTR ps, int length, UINT cp)
{
    std::string ret;
    int r = ::WideCharToMultiByte(cp, 0, ps, length, nullptr, 0, nullptr, nullptr);
    if (r > 0)
    {
        ret.resize(r);
        ::WideCharToMultiByte(cp, 0, ps, length, const_cast<PSTR>(ret.c_str()), r + 1, nullptr, nullptr);
    }
    return ret;
}
#endif // _Z_OS_WINDOWS

} // namespace detail

inline to_u8::to_u8(char32_t c)
{
    int num_bytes, prefix;
    if (c <= 0x7f)
    {
        num_bytes = 0;
        prefix = 0;
    }
    else if (c <= 0x7ff)
    {
        num_bytes = 1;
        prefix = 0xc0;
    }
    else if (c <= 0xffff)
    {
        num_bytes = 2;
        prefix = 0xe0;
    }
    else
    {
        num_bytes = 3;
        prefix = 0xf0;
    }

    m_buffer[m_length++] = prefix | (c >> (num_bytes * 6));
    for (int i = num_bytes - 1; i >= 0; --i)
        m_buffer[m_length++] = 0x80 | (0x3f & (c >> (i * 6)));
}

template <typename CharT, typename E>
u8_iterator<CharT, E>::u8_iterator(const char* ps, size_t left, CharT error_char)
    : m_error_char(error_char), m_ps(ps), m_left(left), m_current_char_length(detail::adjust_char_length(ps, left))
{
}

template <typename CharT, typename E>
CharT u8_iterator<CharT, E>::operator*(void) const
{
    if (0 == m_left)
    {
        ZASSERT(m_left > 0);
        return EOF;
    }

    do {
        if (0 == m_current_char_length)
            break;

        unsigned char c1 = static_cast<unsigned char>(*m_ps);
        if (1 == m_current_char_length)
            return c1;

        unsigned char c2 = static_cast<unsigned char>(m_ps[1]);
        if (2 == m_current_char_length)
            return ((c1 & 0x1f) << 6) | (c2 & 0x3f);

        unsigned char c3 = static_cast<unsigned char>(m_ps[2]);
        if (3 == m_current_char_length)
            return ((c1 & 0xf) << 12) | ((c2 & 0x3f) << 6) | (c3 & 0x3f);

        if constexpr (sizeof(CharT) >= 4)
        {
            unsigned char c4 = static_cast<unsigned char>(m_ps[2]);
            if (4 == m_current_char_length)
                return 0x10000 + (((c1 & 0xf) << 16) | ((c2 & 0x3f) << 10) | ((c3 & 0xf) << 6) | (c4 & 0x3f));
        }
    } while (false);

    return m_error_char;
}

template <typename CharT, typename E>
bool u8_iterator<CharT, E>::operator==(const u8_iterator& o) const
{
    if (m_left > 0)
    {
        if (m_ps != o.m_ps)
            return false;
    }
    return m_left == o.m_left;
}

template <typename CharT, typename E>
void u8_iterator<CharT, E>::operator++()
{
    ZASSERT(m_left > 0);
    if (m_left > 0)
        advance(m_current_char_length > 0 ? m_current_char_length : 1);
}

template <typename CharT, typename E>
void u8_iterator<CharT, E>::advance(size_t count)
{
    ZASSERT(m_left >= count);
    m_ps += count;
    m_left -= count;
    m_current_char_length = detail::adjust_char_length(m_ps, m_left);
}

template <typename CharT, typename E>
to_utf<CharT, E>::to_utf(std::string_view u8s, CharT error_char) : m_u8s(u8s), m_error_char(error_char)
{
}

template <typename CharT, typename E>
u8_iterator<CharT> to_utf<CharT, E>::begin(void) const
{
    return u8_iterator<CharT>(m_u8s.data(), m_u8s.length(), m_error_char);
}

template <typename CharT, typename E>
u8_iterator<CharT> to_utf<CharT, E>::end(void) const
{
    return u8_iterator<CharT>();
}

#ifdef _Z_OS_WINDOWS
inline std::wstring multi_byte_to_wide_string(std::string_view s, UINT cp)
{
    return detail::multi_byte_to_wide_string(s.data(), static_cast<int>(s.length()), cp);
}

inline std::wstring multi_byte_to_wide_string(PCSTR psz, UINT cp)
{
    std::wstring ret = detail::multi_byte_to_wide_string(psz, -1, cp);
    ret.pop_back(); // for the last '\0'
    return ret;
}

inline std::string wide_string_to_multi_byte(std::wstring_view s, UINT cp)
{
    return detail::wide_string_to_multi_byte(s.data(), static_cast<int>(s.length()), cp);
}

inline std::string wide_string_to_multi_byte(PCWSTR psz, UINT cp)
{
    std::string ret = detail::wide_string_to_multi_byte(psz, -1, cp);
    ret.pop_back(); // for the last '\0'
    return ret;
}
#endif // _Z_OS_WINDOWS

} // namespace zed

#endif // ZED_LOCALE_HPP
